import os
import re
import json
import io
import random

from django.core.paginator import Paginator
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse, QueryDict, FileResponse
from django.shortcuts import render, redirect
from django import views
from django_redis import get_redis_connection

from mymall import settings
from mymall.settings import EMAIL_HOST_USER
from django.core.mail import send_mail
from django.contrib.auth.hashers import make_password, check_password
from django.contrib import messages
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout as auth_logout
from .models import Customer, CustomerDetail, ShoppingCar
from .forms import RegisterForm, LoginForm, UserInfoModelForm
from utils.verifyUtil import ImageVerify
from mymall.settings import BASE_DIR
from commodity.models import Column, Menu, CommodityDetail

EMAIL_KEY_TEMPL = 'email:verification:%s'
LOGIN_KEY_TEMPLE = 'login:verification:%s'


# Create your views here.
def index(request):
    columns = Column.objects.filter(status=0)
    menus = Menu.objects.filter(status=0, superior=0)  # 拿一级菜单
    # 构造[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]结构数据
    menu_list = []
    s = []
    for i in range(0, len(menus), 3):
        for j in range(i, i + 3):
            s.append(menus[j])
        menu_list.append(s)
        s = []

    data = {}
    for column in columns:
        data[column.name] = column.commodity.filter(status=0)

    return render(request, 'index.html',
                  {
                      'columns': columns,
                      'data': data,
                      'menus': menus,
                      'menu_list': menu_list,
                  })


class RegisterView(views.View):
    def get(self, request):
        form = RegisterForm()
        return render(request, 'register.html', {'form': form})

    def post(self, request):
        """
        用户注册
        :param request:
        :return:
        """
        post = request.POST
        form = RegisterForm(post)
        if not form.is_valid():
            return render(request, 'register.html', {'form': form})
        email = post.get('email')
        password = make_password(post.get('password'))  # from django.contrib.auth.hashers import make_password
        # 密码加密 文档：https://docs.djangoproject.com/zh-hans/3.2/topics/auth/passwords/
        verify = post.get('verify')

        # 验证邮箱的格式
        re_email = r'(\w+)@(\w+)\.(\w+)'
        if not re.match(re_email, email):
            messages.add_message(request, messages.ERROR, '邮箱格式不对')
            return render(request, 'register.html', {'form': form})

        # 验证码校验
        cache = get_redis_connection(alias='verify_code')
        redis_verification = cache.get(EMAIL_KEY_TEMPL % email)  # 通过key取值

        if (not redis_verification) or (verify != redis_verification):  # 有验证码，且同时验证码和redis数据库保存的一致，才能往下走
            messages.add_message(request, messages.ERROR, '验证码输入错误')
            return render(request, 'register.html', {'form': form})

        cache.delete(EMAIL_KEY_TEMPL % email)  # 释放验证码存储空间，及时清理

        # 查询是否该邮箱是否已经注册
        user_list = Customer.objects.filter(email=email)
        if user_list:
            messages.add_message(request, messages.ERROR, '用户已注册')
            return render(request, 'register.html', {'form': form})

        # 没有注册，新增用户信息,并同步到auth_user
        user = User.objects.create(username=email, password=password)
        Customer.objects.create(email=email, password=password, user=user)
        messages.add_message(request, messages.SUCCESS, '用户注册成功')
        return HttpResponseRedirect('/login/')


class LoginView(views.View):
    def get(self, request):
        form = LoginForm()
        return render(request, 'login.html', {'form': form})

    def post(self, request):
        post = request.POST
        form = RegisterForm(post)
        if not form.is_valid():
            return render(request, 'login.html', {'form': form})

        email = post.get('email')
        password = post.get('password')
        verify = post.get('verify')
        uuid = post.get('uuid')

        # 验证码校验，不区分大小写
        cache = get_redis_connection(alias='verify_code')
        redis_verification = cache.get(LOGIN_KEY_TEMPLE % uuid)  # 通过key取值

        if not redis_verification:  # redis库没有值
            messages.add_message(request, messages.ERROR, '图形验证码已过期')
            return render(request, 'login.html', {'form': form})

        if redis_verification.lower() != verify.lower():  # 验证码输入错误
            messages.add_message(request, messages.ERROR, '验证码输入错误')
            return render(request, 'login.html', {'form': form})

        cache.delete(LOGIN_KEY_TEMPLE % uuid)  # 释放验证码存储空间，及时清理

        # 用户信息校验，mysql数据库展示用户信息，发现密码是加密的因此需要对密码进行处理
        # user = Customer.objects.filter(email=email)  # 结果为QuerySet[]或queryset[object1,...]不会报错中断程序
        user = authenticate(username=email, password=password)  # 或者使用自带的校验
        if not user:  # 没有该用户
            messages.add_message(request, messages.ERROR, '用户与密码不匹配')
            return render(request, 'login.html', {'form': form})
        # if not check_password(password, user[0].password):  # 第一个参数是明文，第二个是密文
        #     messages.add_message(request, messages.ERROR, '密码输入错误')
        #     return render(request, 'login.html', {'form': form})

        # 全部校验通过，设置session，返回成功
        login(request, user)
        request.session['email'] = email
        request.session['total_num'] = ShoppingCar.objects.all().count()
        return HttpResponseRedirect('/')


def logout(request):
    auth_logout(request)
    return HttpResponseRedirect('/')


class EmailVerificationView(views.View):
    DURATION = 5  # 有效时长，单位为分钟

    def post(self, request):
        """
        指定邮箱发送验证码
        :param request:
        :return:
        """
        post = request.POST
        email = post.get('email')

        # 验证邮箱的格式
        re_email = r'(\w+)@(\w+)\.(\w+)'
        if not re.match(re_email, email):
            return HttpResponseRedirect('/register')

        verification_code = random.randint(100000, 999999)  # 六位随机验证码
        print(verification_code)
        # 发送邮件
        self.send_mail_task(email, verification_code, self.DURATION)

        # 将验证码保存到redis数据库
        cache = get_redis_connection(alias='verify_code')  # 获取redis连接对象 from django_redis import get_redis_connection
        cache.set(EMAIL_KEY_TEMPL % email, verification_code, 60 * self.DURATION)  # redis缓存验证码
        # cache.set('email:verification:%s' % email, verification_code, 60 * self.DURATION)
        messages.add_message(request, messages.SUCCESS, '验证码已成功发送')
        form = RegisterForm(request.POST)
        return render(request, 'register.html', {'form': form})

    def send_mail_task(self, email, verification_code, duration):
        """
        发送邮箱验证码
        :param email: 收件人邮箱
        :param verification_code: 随机验证码
        :param duration: 有效时长
        :return:发送状态
        """

        email_param = {
            'subject': 'FFmall验证码',
            'message': '',
            'html_message':  # f-string: 格式化字符串常量，也就是格式化 {} 内容。Python3.6及以后的版本可用
                f"""
                        <h2>欢迎注册FFmall,祝你购物愉快！</h2>
                        <p>您的验证码为：<span style='color:#ff5722'>{verification_code}</span></p>
                        <p>注意：验证码的有效时长为{duration}分钟</p>
                    """,
            'from_email': EMAIL_HOST_USER,  # from FFmall.settings import EMAIL_HOST_USER
            'recipient_list': [email],  # 接收者
        }
        # 验证可以使用后，可注释，打印完成后续
        res_email = send_mail(**email_param)  # from django.core.mail import send_mail
        return res_email

    """
    email_param = {
            'subject': 'FFmall验证码',
            'message': '',
            'from_email': '157721832@163.com',  # from FFmall.settings import EMAIL_HOST_USER
            'recipient_list': ['117220100@qq.com'],  # 接收者
        }
    """


# 图片验证码视图类
class ImageVerifyView(views.View):
    EXPIRE_TIME = 60 * 3

    def get(self, request, uuid):  # uuid由前端生成，传入后台
        imgVerify = ImageVerify()
        img, code = imgVerify.image_code()
        print(code)
        # 创建一个字节流管道
        img_bytes = io.BytesIO()
        # 将图片数据存入字节流管道， format可以按照具体文件的格式填写
        img.save(img_bytes, format="PNG")
        # 从字节流管道中获取二进制
        image_bytes = img_bytes.getvalue()

        cache = get_redis_connection(alias='verify_code')
        cache.set(LOGIN_KEY_TEMPLE % uuid, code, self.EXPIRE_TIME)  # redis缓存验证码

        return HttpResponse(image_bytes, content_type="image/png")


# 用户个人信息
@method_decorator(login_required, name='dispatch')
class UserDetailView(views.View):
    def get(self, request):
        # 是否是登录用户
        email = request.session.get('email')  # 获取session信息，确认登录情况
        # if not email:
        #     messages.add_message(request, messages.ERROR, '没有登录')
        #     return render(request, 'login.html', {'form': LoginForm()})
        # 拿到邮箱账号，同mysql数据库中信息进行校验
        customer = Customer.objects.get(email=email)
        # 根据用户反向查询用户详情
        try:
            customer_detail = customer.customerdetail
        except Exception as e:  # 当前用户还没有详情
            print(f'该用户还无详情：{e}')
            customer_detail = CustomerDetail()
            customer_detail.customer = customer  # 正向通过属性关联
            customer_detail.save()
        form = UserInfoModelForm(instance=customer_detail)

        return render(request, 'info.html', {'user': customer, 'form': form})

    def post(self, request):
        post = request.POST
        form = UserInfoModelForm(post)
        if not form.is_valid():
            return render(request, 'info.html', {'form': form})

        nickname = post.get('nickname')
        phone = post.get('phone')
        age = post.get('age')
        sex = post.get('sex')
        address = post.get('address')
        birthday = post.get('birthday')

        # 解读令牌中的信息，拿到邮箱账号,获取用户
        customer = Customer.objects.get(email=request.session.get('email'))

        # 根据用户反向查询用户详情
        try:
            customer_detail = customer.customerdetail
        except:  # 当前用户还没有详情
            customer_detail = CustomerDetail()
            customer_detail.user = customer  # 正向通过属性关联

        # 保存用户信息
        customer_detail.nickname = nickname
        customer_detail.phone = phone
        customer_detail.age = age
        customer_detail.sex = sex
        customer_detail.address = address
        customer_detail.birthday = birthday
        customer_detail.save()
        messages.add_message(request, messages.SUCCESS, '个人信息保存成功')
        return HttpResponseRedirect('/')


def get_avatar(request, path):
    print(os.path.join(settings.MEDIA_ROOT, path))
    return FileResponse(open(os.path.join(settings.MEDIA_ROOT, path), 'rb'))


# 头像上传
@method_decorator(login_required, name='dispatch')
class PortraitView(views.View):
    def post(self, request):
        # 获取请求中的文件数据
        files = request.FILES.get('file')

        # 校验（没有上传文件或者文件类型不是图片）
        if not files or files.content_type not in ('image/jpeg', 'image/png', 'image/jpg'):
            messages.add_message(request, messages.WARNING, '上传图片格式不正确')
            return HttpResponseRedirect('/detail/')  # 返回参数错误

        # 成功，获取图片类型(即图片后缀名)
        try:
            image_ext_name = files.name.split('.')[-1]
        except Exception as e:
            print(f'{e}原文件无后缀，使用 png 为默认后缀')
            image_ext_name = 'png'
        filename = 'avatar' + '.' + image_ext_name
        image_url = '/uploads/' + filename
        # 将图片上传到uploads
        try:  # 上传可能出错
            with open(BASE_DIR + image_url, 'wb') as f:
                for i in files.chunks():
                    f.write(i)
        except Exception as e:
            messages.add_message(request, messages.WARNING, f'图片上传异常{e}')
            return HttpResponseRedirect('/detail/')
        else:
            # 保存数据库
            customer = Customer.objects.get(email=request.session.get('email'))
            # 根据用户反向查询用户详情
            try:
                customer_detail = customer.customerdetail
            except:  # 当前用户还没有详情
                customer_detail = CustomerDetail()
                customer_detail.customer = customer  # 正向通过属性关联
            # 图片地址存入
            customer_detail.portrait = image_url
            customer_detail.save()
            messages.add_message(request, messages.SUCCESS, '图片上传成功')
            # return HttpResponseRedirect('/detail/')
            return JsonResponse({'image_url': image_url})


# 购物车
@method_decorator(login_required, name='dispatch')
class ShoppingCarView(views.View):
    def get(self, request):
        """
        购物车查询功能
        :param request:
        :return:
        """
        shopping_cars = ShoppingCar.objects.filter(user=request.user.customer).order_by('-create_time')
        # 实现分页
        total_num = shopping_cars.count()  # 总数据量
        request.session['total_num'] = total_num
        page = request.GET.get('page', 1)  # 当前页码
        per_page = request.GET.get('per_page', 10)  # 每页数据条数

        paginator = Paginator(shopping_cars, per_page, allow_empty_first_page=True)
        shopping_cars = paginator.get_page(page)  # 每页显示数据，
        total_page = paginator.num_pages  # 总页数
        return render(request, 'shopping_car.html',
                      {'shopping_car_commodities': shopping_cars,
                       'total_page': total_page,
                       'page': page,
                       'per_page': per_page,
                       'total_num': total_num,
                       })

    def post(self, request):
        """
        功能:加入购物车
        根据模型，我们需要如下数据
        需要商品选项：多个选项，你可以获取到该商品配置的库存，该商品配置的金额
        需要购买商品的数量
        需要商品
        需要用户
        :param request:
        :return:
        """
        post = request.POST
        id = post.get('id')
        options = post.get('options')
        options = json.loads(options)
        number = post.get('number')

        # 获取商品选项
        money = None
        inventory = None
        subject = ''
        options_id = ''
        commodity_options = CommodityDetail.objects.filter(id__in=options).values()
        for option in commodity_options:
            options_price = option.get('price', None)
            options_inventory = option.get('inventory', None)
            if options_price:
                money = options_price
            if options_inventory:
                inventory = options_inventory

            subject += option['option_name'] + ":" + option['options'] + "。|"
            options_id += ' ' + str(option.get('id'))
        if inventory is None or inventory < int(number):
            messages.add_message(request, messages.WARNING, '库存不足')
            return HttpResponseRedirect(f'/commodity/get_detail/?id={id}')

        # 获取用户
        user = request.user.customer

        # 创建购物车数据
        try:
            ShoppingCar.objects.create(money=money, subject=subject, number=number, user=user, commodity_id=id,
                                       options=options_id.strip(' '))
        except Exception as e:
            messages.add_message(request, messages.ERROR, '购物车加入失败')
            return HttpResponseRedirect(f'/commodity/get_detail/?id={id}')
        else:
            messages.add_message(request, messages.SUCCESS, '购物车加入成功')
            # return HttpResponseRedirect(f'/commodity/get_detail/?id={id}')
            return JsonResponse({'code': 200})

    def put(self, request):
        # 获取参数
        put = QueryDict(request.body)  # from django.http import QueryDict
        id = put.get('id')
        number = put.get('number')
        options = ShoppingCar.objects.get(id=id).options.split(' ')

        # 获取商品选项
        inventory = None
        commodity_options = CommodityDetail.objects.filter(id__in=options).values()
        for option in commodity_options:
            options_inventory = option.get('inventory', None)
            if options_inventory:
                inventory = options_inventory

        if inventory is None or inventory < int(number):
            messages.add_message(request, messages.WARNING, '库存不足')
            return HttpResponseRedirect('/shopping_car/', status=303)
        try:
            shopping_car = ShoppingCar.objects.get(id=id)
            shopping_car.number = number
            shopping_car.save()
        except Exception as e:
            messages.add_message(request, messages.ERROR, f'修改失败:{e}')
            return HttpResponseRedirect('/shopping_car/', status=303)
        else:
            messages.add_message(request, messages.SUCCESS, '修改成功')
            return HttpResponseRedirect('/shopping_car/', status=303)

    def delete(self, request):
        data = QueryDict(request.body)
        id = data.get('id')
        try:
            shopping_car = ShoppingCar.objects.get(id=id)
            shopping_car.delete()
        except Exception as e:
            messages.add_message(request, messages.ERROR, f'删除失败:{e}')
            return HttpResponseRedirect('/shopping_car/', status=303)
        else:
            messages.add_message(request, messages.SUCCESS, '删除成功')
            return HttpResponseRedirect('/shopping_car/', status=303)


def page_not_found(request, exception):
    return render(request, '404.html', status=404)
